分类
联系方式
  1. 新浪微博
  2. E-mail

DartVM SendPort

介绍

端口(Port)是 Dart 虚拟机底层一种重要通信方式,对于开发者来说,比较熟悉的是 Isolate 间通信,需要通过 ReceivePort 和 SendPort 实现。

SendPort 由 ReceivePort 创建,负责向 ReceivePort 发送信息。每个 SendPort 与一个 ReceivePort 对应。一个 ReceivePort 可以有多个 SendPort。

注意:SendPort 是可以跨 Isolate 传递的,通常用于 Isolate 双向通信机制。

支持类型

都有哪些数据类型支持通过 Port 传递呢?

数据类型包括:Null、bool、int、double、String、List、Map、TransferableTypedData、SendPort、Capability。

SendPort(Dart)

在 Dart 层,SendPort 是一个抽象类,实现比较简单,核心只有一个 send 方法。

send 方法

// 消息会立刻发出,不会阻塞
// 消息会被放到消息队列上,直到传递给接收方
void send(Object? message);

_SendPortImpl(Dart)

_SendPortImpl 是 SendPort 在 Dart 的实现类。核心实现如下:

@pragma("vm:entry-point")
class _SendPortImpl implements SendPort {
  // ...

  /*--- public interface ---*/
  @pragma("vm:entry-point", "call")
  void send(var message) {
    _sendInternal(message);
  }

  /*--- private implementation ---*/
  _get_id() native "SendPortImpl_get_id";
  _get_hashcode() native "SendPortImpl_get_hashcode";

  // Forward the implementation of sending messages to the VM.
  void _sendInternal(var message) native "SendPortImpl_sendInternal_";
}

可以看到:

  • send 方法通过 C/C++ 层的 SendPortImpl_sendInternal_ 实现
  • SendPort 还有一个 id 属性,也是通过 C/C++ 层的 SendPortImpl_get_id 获取

SendPort 与 _SendPortImpl 的关联

_SendPortImpl 是怎么与 SendPort 相关联的呢?

答案在 runtime/vm/object.cc 的 Object::Init 方法,这是是在 Isolate 创建的时候进行的一些初始化工作。比如,指定了当 new 一个类的时候,可以由另一个类完成实现:

cls = Class::New<ReceivePort, RTN::ReceivePort>(isolate_group);
RegisterPrivateClass(cls, Symbols::_RawReceivePortImpl(), isolate_lib);
pending_classes.Add(cls);

cls = Class::New<SendPort, RTN::SendPort>(isolate_group);
RegisterPrivateClass(cls, Symbols::_SendPortImpl(), isolate_lib);
pending_classes.Add(cls);

其中,Symbols::_SendPortImpl 定义在 runtime/vm/symbols.h,是一个字符串:

V(_SendPortImpl, "_SendPortImpl")

这样就能够跟 _SendPortImpl 的 entry-point 注解对应上了。

SendPortImpl_sendInternal_

这个方法非常关键。

DEFINE_NATIVE_ENTRY(SendPortImpl_sendInternal_, 0, 2) {
  // 参数解析,第1个是Port,第2个是消息
  // SendPort 是C++ 层的
  GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
  GET_NON_NULL_NATIVE_ARGUMENT(Instance, obj, arguments->NativeArgAt(1));

  // 从 port 中获取端口号
  const Dart_Port destination_port_id = port.Id();
  const bool can_send_any_object = isolate->origin_id() == port.origin_id();

  // 解析 message,Post
  // 消息有 3 个参数:端口号、消息内容、优先级
  if (ApiObjectConverter::CanConvert(obj.ptr())) {
    PortMap::PostMessage(
        Message::New(destination_port_id, obj.ptr(), Message::kNormalPriority));
  } else {
    const bool same_group = FLAG_enable_isolate_groups &&
                            PortMap::IsReceiverInThisIsolateGroup(
                                destination_port_id, isolate->group());
    if (same_group) {
      const auto& copy = Object::Handle(CopyMutableObjectGraph(obj));
      auto handle = isolate->group()->api_state()->AllocatePersistentHandle();
      handle->set_ptr(copy.ptr());
      std::unique_ptr<Message> message(
          new Message(destination_port_id, handle, Message::kNormalPriority));
      PortMap::PostMessage(std::move(message));
    } else {
      // TODO(turnidge): Throw an exception when the return value is false?
      PortMap::PostMessage(WriteMessage(can_send_any_object, obj,
                                        destination_port_id,
                                        Message::kNormalPriority));
    }
  }
  return Object::null();
}

其中,可以概括为 2 个步骤:

  1. 解析消息
  2. PortMap::PostMessage 向消息队列抛消息,这是一个非常重要的方法,将在 PortMap 一文中梳理

代码中的 Dart_Port 是一个整数:

typedef int64_t Dart_Port;

SendPort(C++)

C++ 层的 SendPort 位于 runtime/vm/object.h 中,继承自 C++ 层的 Instance 类。

伴生实例

这个类的实现上有点奇怪,不是一个简单的 C++ 类,感觉是跟 Dart 侧实例共生的 C++ 实例。

目前从感觉上来说:Dart 侧创建一个 SendPort 实例,同时会创建一个伴生的 C++ 实例。

再回到这段初始化 C++ 代码:

cls = Class::New<SendPort, RTN::SendPort>(isolate_group);
RegisterPrivateClass(cls, Symbols::_SendPortImpl(), isolate_lib);
pending_classes.Add(cls);

这里面包含几重意思:

  1. New 泛型的第一个,是 C++ 侧的 SendPort
  2. New 泛型的第二个,是 runtime/vm/compiler/runtime_api.h,这个作用还不太理解,看起来和编译器相关
  3. _SendPortImpl 关联的是 Dart 侧的实现类
  4. _SendPortImpl 实现了 SendPort 接口,因此上层业务可以使用 SendPort 接口来使用

进一步总结一下:这是一种将 C++ 类导入到 Dart 世界中的方法。

为什么需要 C++ 实现?

如果 SendPort 只是保留一个整数类型的端口,为什么要大费周折,把一半实现放在 Native,一半实现放在 Flutter?

理由:

  • id 参数(端口)只是 SendPort 存储的状态,这个类的状态的确比较简单
  • 更加重要的是 SendPort 的 send 方法,这个方法需要来到 C/C++ 层(SendPortImpl_sendInternal_)访问消息队列